package com.wcj.study.multidatasource.config;

import java.lang.reflect.Method;
import java.util.List;

import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import com.wcj.study.multidatasource.config.MultiDataSourceProperties.DsGroupProperties;

@Component
@Aspect
@Order(-100)
public class MultiDataSourceAspect implements EnvironmentAware {

    public static final Logger LOGGER = LoggerFactory.getLogger(MultiDataSourceAspect.class);

    private Environment env;

    @Autowired
    private MultiDataSourceProperties multiDataSourceProperties;

    // @Before(value = "@annotation(com.xhsr.commons.multidatasource.config.MultiDataSource)")
    @Before(value = "@within(com.xhsr.commons.multidatasource.config.MultiDataSource)")
    public void doBefore(JoinPoint joinPoint) throws Throwable {

        // 当前线程已经设置了数据源,直接返回
        if (StringUtils.isNoneBlank(MultiDataSourceContext.get())) {
            return;
        }

        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature)signature;
        Object target = joinPoint.getTarget();
        // 类上的注解
        MultiDataSource dsClass = target.getClass().getAnnotation(MultiDataSource.class);

        // 方法上的注解
        Method method = methodSignature.getMethod();
        String methodName = method.getName();
        MultiDataSource dsMethod = method.getAnnotation(MultiDataSource.class);

        // 数据源组名称
        String dsGroup = null;
        DbOperateType opType = null;

        // 数据源名称
        String dsName = null;

        // 查找数据源组
        // 方法上查找,如果方法上
        if (dsMethod != null) {
            dsGroup = dsMethod.dsGroup();
        } else if (dsClass != null) {
            // 方法上没有配置数据源,取类上的数据源
            if (StringUtils.isBlank(dsGroup)) {
                dsGroup = dsClass.dsGroup();
            }
        }
        // 方法上和类上都没有配置数据源,取默认配置的
        if (StringUtils.isBlank(dsGroup)) {
            dsGroup = multiDataSourceProperties.getDefaultDsGroup();
        }

        // 查找操作类型
        if (dsMethod != null) {
            // 先从方法上查找
            opType = dsMethod.opType();
        } else if (dsClass != null) {
            // 方法上没有,在类上面查找
            if (opType == null || opType == DbOperateType.DEFAULT) {
                opType = dsClass.opType();
            }
        }

        // 方法和类上的注解,都没有配置opType,根据方法名上配置的规则查找操作类型
        if (opType == null || opType == DbOperateType.DEFAULT) {
            // 通过方法名确定读写操作
            AntPathMatcher matcher = new AntPathMatcher();
            boolean hasWrite = false;

            // 一组数据源配置的读写策略
            DsGroupProperties dsGroupProperties = multiDataSourceProperties.getInstance().get(dsGroup);

            // 判断写操作
            for (String name : dsGroupProperties.getWrite().getOpMethods()) {
                boolean isPass = matcher.match(name, methodName);
                if (isPass) {
                    opType = DbOperateType.WRITE;
                    hasWrite = true;
                    break;
                }
            }

            // 判断读操作
            if (hasWrite) {
                for (String name : dsGroupProperties.getRead().getOpMethods()) {
                    boolean isPass = matcher.match(name, methodName);
                    if (isPass) {
                        opType = DbOperateType.READ;
                        break;
                    }
                }
            }

            // 都没有,默认写操作
            if (opType == null || opType == DbOperateType.DEFAULT) {
                opType = DbOperateType.WRITE;
            }

        }
        
        dsName = getDsName(dsGroup, opType);
        LOGGER.info("数据源组====>" + dsGroup + ",数据源====>" + dsName + ",操作类型====>" + opType.name());
        MultiDataSourceContext.set(dsName);

    }

    /**
     * 
     *
     * @param dsGroup 数据源组名称
     * @param opTypes 操作类型
     * @return
     */
    private String getDsName(String dsGroup, DbOperateType opType) {

        // 类上没有配置数据源组,取默认的数据源组
        if (StringUtils.isBlank(dsGroup)) {
            dsGroup = multiDataSourceProperties.getDefaultDsGroup();
        }

        if (dsGroup.startsWith("${") && dsGroup.endsWith("}")) {
            String key = dsGroup.substring(2, dsGroup.length() - 1);
            dsGroup = env.getProperty(key, String.class);
        }

        // 注解上配置的数据源名称
        DsGroupProperties dsGroupProperties = multiDataSourceProperties.getInstance().get(dsGroup);

        List<String> dsList = null;
        // 注解上配置的操作类型,优先查找写操作
        switch (opType) {
            case READ:
                dsList = dsGroupProperties.getRead().getDsBeanNames();
                break;
            case WRITE:
                dsList = dsGroupProperties.getWrite().getDsBeanNames();
                break;
            default:
                dsList = dsGroupProperties.getWrite().getDsBeanNames();
                break;
        }

        // 随机挑选一个
        return dsList.get(RandomUtils.nextInt(0, dsList.size()));

    }

    @After(value = "@within(com.xhsr.commons.multidatasource.config.MultiDataSource)")
    public void doAfter(JoinPoint joinPoint) throws Throwable {
        MultiDataSourceContext.remove();
    }

    /**
     * TODO 添加方法说明
     *
     * @param environment
     */
    @Override
    public void setEnvironment(Environment environment) {
        this.env = environment;
    }

}
